<template>
  <div class="preview-container">
    <!-- 预览控制栏 -->
    <div class="preview-toolbar">
      <div class="preview-controls">
        <span class="control-label">预览</span>
        <div class="toggle-switch">
          <input type="checkbox" id="autoRefresh" v-model="autoRefresh" />
          <label for="autoRefresh" class="toggle-label"></label>
        </div>
        <button class="refresh-btn" @click="manualRefresh" title="刷新预览">
          <svg viewBox="0 0 24 24" width="16" height="16">
            <path
              d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 9h7V2l-2.35 4.35z"
              fill="currentColor"
            />
          </svg>
        </button>
      </div>
    </div>

    <!-- 预览内容 -->
    <div class="preview-content">
      <div v-if="loading" class="loading">
        <div class="spinner"></div>
        <span>加载中...</span>
      </div>
      <div v-if="error" class="error">
        <span>{{ error }}</span>
      </div>
      <iframe
        ref="iframeRef"
        class="preview-iframe"
        sandbox="allow-scripts allow-same-origin"
        @load="handleIframeLoad"
      ></iframe>
    </div>

    <!-- 控制台输出面板 -->
    <div class="console-panel" v-if="showConsole">
      <div class="console-header">
        <span>控制台输出</span>
        <button class="console-clear" @click="clearConsole">清除</button>
        <button class="console-close" @click="showConsole = false">关闭</button>
      </div>
      <div class="console-content">
        <div
          v-for="(log, index) in consoleLogs"
          :key="index"
          :class="['console-log', log.type]"
        >
          <span class="log-time">{{ log.time }}</span>
          <span class="log-content">{{ log.content }}</span>
        </div>
      </div>
    </div>

    <!-- 控制台切换按钮 -->
    <button
      class="console-toggle"
      @click="showConsole = !showConsole"
      :class="{ active: showConsole }"
    >
      {{ showConsole ? "隐藏控制台" : "显示控制台" }}
    </button>
  </div>
</template>

<script setup>
import { ref, onMounted, watch, onBeforeUnmount, reactive } from "vue";

const props = defineProps({
  htmlCode: {
    type: String,
    default: "",
  },
  cssCode: {
    type: String,
    default: "",
  },
  jsCode: {
    type: String,
    default: "",
  },
  timestamp: {
    type: Number,
    default: 0,
  },
});

const iframeRef = ref(null);
const loading = ref(true);
const error = ref("");
const showConsole = ref(false);
const consoleLogs = ref([]);
const autoRefresh = ref(false); // 默认开启自动刷新

// 默认导入映射配置
const defaultImportMaps = reactive({
  imports: {
    vue: "https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.prod.js",
    react: "https://esm.sh/react@18.2.0",
    "react-dom": "https://esm.sh/react-dom@18.2.0",
    "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
    lodash: "https://esm.sh/lodash@4.17.21",
    "lodash-es": "https://esm.sh/lodash-es@4.17.21",
    three: "https://esm.sh/three@0.150.1",
    axios: "https://esm.sh/axios@1.3.4",
    dayjs: "https://esm.sh/dayjs@1.11.7",
    "dayjs/locale/zh-cn": "https://esm.sh/dayjs@1.11.7/locale/zh-cn",
    echarts: "https://esm.sh/echarts@5.4.2",
  },
});

// 手动刷新预览
const manualRefresh = () => {
  updatePreview();
};

// 清除控制台日志
const clearConsole = () => {
  consoleLogs.value = [];
};

// 添加控制台日志
const addConsoleLog = (type, content) => {
  const now = new Date();
  const time = `${now.getHours().toString().padStart(2, "0")}:${now
    .getMinutes()
    .toString()
    .padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`;

  consoleLogs.value.push({
    type,
    content: typeof content === "object" ? JSON.stringify(content) : content,
    time,
  });

  // 限制日志数量
  if (consoleLogs.value.length > 100) {
    consoleLogs.value = consoleLogs.value.slice(-100);
  }
};

// 更新预览内容
const updatePreview = () => {
  if (!iframeRef.value) return;

  try {
    error.value = "";
    loading.value = true;

    // 导入映射JSON
    const importMapJson = JSON.stringify(defaultImportMaps, null, 2);

    // 组合HTML、CSS和JS代码
    const combinedCode = `
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>代码预览</title>
        <script type="importmap">
          ${importMapJson}
        <\/script>
        <style>
          ${props.cssCode}
        </style>
      </head>
      <body>
        ${props.htmlCode}
        <script>
          // 重写控制台方法
          (function() {
            const originalConsole = {
              log: console.log,
              error: console.error,
              warn: console.warn,
              info: console.info
            };

            // 重写console.log
            console.log = function() {
              originalConsole.log.apply(console, arguments);
              window.parent.postMessage({
                type: 'console',
                method: 'log',
                content: Array.from(arguments).map(arg =>
                  typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
                ).join(' ')
              }, '*');
            };

            // 重写console.error
            console.error = function() {
              originalConsole.error.apply(console, arguments);
              window.parent.postMessage({
                type: 'console',
                method: 'error',
                content: Array.from(arguments).map(arg =>
                  typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
                ).join(' ')
              }, '*');
            };

            // 重写console.warn
            console.warn = function() {
              originalConsole.warn.apply(console, arguments);
              window.parent.postMessage({
                type: 'console',
                method: 'warn',
                content: Array.from(arguments).map(arg =>
                  typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
                ).join(' ')
              }, '*');
            };

            // 重写console.info
            console.info = function() {
              originalConsole.info.apply(console, arguments);
              window.parent.postMessage({
                type: 'console',
                method: 'info',
                content: Array.from(arguments).map(arg =>
                  typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
                ).join(' ')
              }, '*');
            };

            // 捕获全局错误
            window.onerror = function(message, source, lineno, colno, error) {
              window.parent.postMessage({
                type: 'error',
                message: message
              }, '*');
              return true;
            };

            // 捕获未处理的Promise拒绝
            window.addEventListener('unhandledrejection', function(event) {
              console.error('Unhandled Promise Rejection:', event.reason);
            });
          })();
        <\/script>
         <script type="module">
          // 动态模块加载器 - 使用Blob URL方法
          (async function() {
            try {
              // 获取JavaScript代码
              const jsCode = ${JSON.stringify(props.jsCode)};
              
              // 创建Blob并获取URL
              function getBlobURL(code, type = 'text/javascript') {
                const blob = new Blob([code], { type });
                const blobURL = URL.createObjectURL(blob);
                return blobURL;
              }
              
              // 创建模块URL
              const moduleURL = getBlobURL(jsCode);
              
              try {
                // 动态导入模块
                await import(moduleURL);
                console.log("模块已成功加载和执行");
              } catch (execErr) {
                // 关键：增强错误提示
                window.parent.postMessage({
                  type: 'error',
                  message: "运行时错误: " + (execErr && execErr.message ? execErr.message : execErr) + "。常见原因：可能是 DOM 元素未挂载或 ref 为空。请检查相关代码，避免在 DOM 未就绪时访问 ref/DOM。"
                }, '*');
                throw execErr;
              } finally {
                // 释放Blob URL
                URL.revokeObjectURL(moduleURL);
              }
            } catch (err) {
              window.parent.postMessage({
                type: 'error',
                message: "模块处理错误: " + (err && err.message ? err.message : err) + "。常见原因：可能是 DOM 元素未挂载或 ref 为空。"
              }, '*');
            }
          })();
        <\/script>
      </body>
      </html>
    `;

    // 使用Blob和URL.createObjectURL创建一个临时URL
    const blob = new Blob([combinedCode], { type: "text/html" });
    const url = URL.createObjectURL(blob);

    // 设置iframe的src
    iframeRef.value.src = url;

    // 清理URL对象
    setTimeout(() => {
      URL.revokeObjectURL(url);
    }, 100);
  } catch (err) {
    console.error("更新预览错误:", err);
    error.value = `预览错误: ${err.message}`;
    loading.value = false;
  }
};

// 监听来自iframe的消息
const handleMessage = (event) => {
  if (event.data) {
    if (event.data.type === "error") {
      error.value = `运行错误: ${event.data.message}`;
    } else if (event.data.type === "console") {
      addConsoleLog(event.data.method, event.data.content);
    }
  }
};

// iframe加载完成
const handleIframeLoad = () => {
  loading.value = false;
};

// 监听代码变化，更新预览
watch(
  () => [props.htmlCode, props.cssCode, props.jsCode, props.timestamp],
  () => {
    if (autoRefresh.value || props.timestamp > 0) {
      updatePreview();
    }
  },
  { immediate: true }
);

onMounted(() => {
  window.addEventListener("message", handleMessage);
  updatePreview();
});

onBeforeUnmount(() => {
  window.removeEventListener("message", handleMessage);
});
</script>

<style scoped>
.preview-container {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #fff;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* 预览工具栏样式 */
.preview-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 30px;
  padding: 0 10px;
  background-color: #1e1e1e;
  border-bottom: 1px solid #333;
}

.preview-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.control-label {
  color: #ddd;
  font-size: 12px;
}

/* 切换开关样式 */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 18px;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-label {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #333;
  border-radius: 18px;
  transition: 0.3s;
}

.toggle-label:before {
  position: absolute;
  content: "";
  height: 14px;
  width: 14px;
  left: 2px;
  bottom: 2px;
  background-color: #ddd;
  border-radius: 50%;
  transition: 0.3s;
}

input:checked + .toggle-label {
  background-color: #0e639c;
}

input:checked + .toggle-label:before {
  transform: translateX(18px);
}

/* 刷新按钮样式 */
.refresh-btn {
  background: none;
  border: none;
  color: #ddd;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3px;
  border-radius: 3px;
}

.refresh-btn:hover {
  background-color: #3a3f4b;
}

.preview-content {
  flex: 1;
  position: relative;
  width: 100%;
  height: 100%;
  background-color: white;
}

.preview-iframe {
  width: 100%;
  height: 100%;
  border: none;
  background-color: white;
}

.loading {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgba(40, 44, 52, 0.8);
  color: #f0f0f0;
  z-index: 10;
}

.spinner {
  width: 40px;
  height: 40px;
  margin-bottom: 10px;
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  border-top-color: #fff;
  animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.error {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 10px;
  background-color: rgba(255, 0, 0, 0.7);
  color: white;
  z-index: 20;
  font-family: monospace;
  white-space: pre-wrap;
  max-height: 150px;
  overflow: auto;
}

/* 控制台样式 */
.console-panel {
  height: 200px;
  background-color: #1e1e1e;
  color: #f0f0f0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid #555;
  font-family: "Consolas", monospace;
}

.console-header {
  display: flex;
  justify-content: space-between;
  padding: 5px 10px;
  background-color: #2d2d2d;
  border-bottom: 1px solid #555;
}

.console-content {
  flex: 1;
  overflow-y: auto;
  padding: 5px;
}

.console-log {
  margin: 2px 0;
  padding: 3px 5px;
  border-radius: 2px;
  font-size: 12px;
  display: flex;
}

.log-time {
  color: #888;
  margin-right: 8px;
  font-size: 11px;
}

.console-log.log .log-content {
  color: #f0f0f0;
}

.console-log.error .log-content {
  color: #ff6b6b;
}

.console-log.warn .log-content {
  color: #ffd700;
}

.console-log.info .log-content {
  color: #61dafb;
}

.console-clear,
.console-close {
  background: none;
  border: none;
  color: #ddd;
  cursor: pointer;
  font-size: 12px;
  padding: 2px 5px;
}

.console-clear:hover,
.console-close:hover {
  color: white;
}

.console-toggle {
  position: absolute;
  bottom: 10px;
  right: 10px;
  background-color: #2d2d2d;
  color: #ddd;
  border: 1px solid #555;
  border-radius: 4px;
  padding: 5px 10px;
  cursor: pointer;
  font-size: 12px;
  z-index: 14;
}

.console-toggle.active {
  background-color: #3a3f4b;
}

.console-toggle:hover {
  background-color: #3a3f4b;
}
</style>
